/**
 * Created with IntelliJ IDEA.
 * User: zzl
 * Date: 12-11-12
 * To change this template use File | Settings | File Templates.
 */
/**
 *  conventions:
 *
 *      nodes are trs in tbody with class 'nodes'
 *      if no nodes, item in table with class '.empty-indicator' will be shown
 *      th with a 'data-treeCol' attribute will be the tree column, if none, use first th
 *      nodes will have a class 'node'
 *      'data-level' attribute on node defines it's level in tree
 *      td content in tree col will be wrapped by a div with 'node-content', 'cell-wrapper' classes
 *
 */
(function ($, fnName, undefined) {

    function hasChildren(node) {
        var children = $(node).data('children');
        return children && children.length;
    }

    var levelIndent = 24;

    var TreeTable = function (table, options) {
        this.$table = $(table);
        this.options = options;
        this.setup();
    };

    var proto = {constructor: TreeTable};
    TreeTable.prototype = proto;

    proto.setup = function () {
        var me = this;
        var $table = me.$table;

        me.rootNode = domex.getFirstChildElelement($table.children('tbody.nodes')[0], 'TR');
        var $trs = $(me.rootNode).nextAll();
        $table.find('.empty-indicator').toggle(!$trs.length);

        me.treeCol = Math.max($table.find('>thead>tr>th[data-treeCol]').index(), 0);

//        me.buildTree($table, $trs);
        //ttt
//        me.updateAltRows();

        //
        $table.on('click.' + fnName, '.node-button', function (e) {
            e.stopPropagation();
            var $tr = $(this).parents('tr:first');
            if ($tr.is('.parent')) {
                me.toggleNode($tr);
            }
        });
        $table.on('mousedown.' + fnName, '>tbody>tr', function () {
            $(this).data('clicking', true);
        });
        $table.on('mousemove.' + fnName, '>tbody>tr', function () {
            $(this).data('clicking', false);
        });
        $table.on('dblclick.' + fnName, '>tbody>tr.parent', function (e) {
            e.preventDefault();
            domex.clearSelection();
            me.toggleNode($(this));
        });
        $table.on('selectstart.' + fnName, '>tbody>tr.parent', function (e) {
            if ($(this).data('clicking')) {
                e.preventDefault();
            }
        });
    };

    proto.deleteNode = function (nodes) {
        var me = this;
        var $table = me.$table;

        var parentNodes = [];
        for (var n = 0; n < nodes.length; n++) {
            var node = nodes[n];
            var parentNode = me.getParentNode(node);
            parentNodes.push(parentNode);
            var $parentNode = $(parentNode);
            var children = $parentNode.data('children');
            children.splice(jsex.indexOf(children, node), 1);
            if (!children.length) {
                $parentNode.removeClass('parent expanded collapsed').addClass('leaf');
            }

            me.deleteBranch(node);
        }
        parentNodes = jsex.distinct(parentNodes);
        if(jsex.inArray(me.rootNode, parentNodes)) {
            parentNodes = $(me.rootNode).data('children');
        }
        if(parentNodes.length) {
            me.updateIndentLinesExtended(parentNodes);
        }
//        if(!domex.getNextElement(me.rootNode)) {
//        if (!$table.find('>tbody.nodes').children().length) {
//            $table.find('.empty-indicator').show();
//        }
        me.$table.trigger('nodeDeleted');
    };

    proto.getSelectedSeparatedBranchNodes = function () {
        var me = this;
        var nodes = me.$table.triggerHandler('getSelectedRequest');
        var allNodes = me.$table.find('tbody.nodes>tr');
        nodes.sort(function (node1, node2) {
            return $.inArray(node1, allNodes) - $.inArray(node2, allNodes);
        });
        me.filterOutBranchDescendantsForSortedNodes(nodes);
        return nodes;
    };

    ///////////
    proto.buildTree = function ($table, $trs, dynamic) {
        var me = this;

        var nodePath = [];
        if(dynamic) {
            var $dummyRoot = $(me.rootNode).clone();
            $dummyRoot.data('children', []);
            nodePath[-1] = $dummyRoot[0];
        }
        else {
            nodePath[-1] = me.rootNode;
        }


        var parentTr;
        var prevLevel = -1;
        var children;
        for (var n = 0; n < $trs.length; n++) {
            var tr = $trs[n];
            var $tr = $(tr);
            $tr.addClass('node');
            var level = parseInt($(tr).data('level'));
            if (!level) {
                level = 0;

                $(tr).dataAttr('level', level);
            }
            nodePath[level] = tr;

            if (level != prevLevel) {
                parentTr = nodePath[level - 1];
                var $parentTr = $(parentTr);
                children = $parentTr.data('children');
                if (!children) {
                    children = [];
                    $parentTr.data('children', children);
                }
            }
            children.push(tr);
            $tr.data('parent', parentTr);

            //var $td = $(tr).find('td:nth-child(' + (me.treeCol + 1) + ')');
            var $td = $($(tr).children()[me.treeCol]);
            var pad = levelIndent * level + 24;

            $td[0].innerHTML = '<div class="indent-wrapper"><div class="indent"></div></div>' +
                '<div class="node-content cell-wrapper" style="padding-left: ' + pad + 'px">' +
                '<div class="node-button"></div>' +
                $td[0].innerHTML + '</div>';

            prevLevel = level;
        }
        //
        for (n = 0; n < $trs.length; n++) {
            tr = $trs[n];
            me.updateIndentLines(tr);
        }
        //
        me.setupLevel($table, $trs);

    };

    proto.initNode = function(tr) {
        var me = this;
        if(tr.length) {
            for(var n = 0; n < tr.length; n++) {
                me.initNode(tr[n]);
            }
            return;
        }
        var $td = $($(tr).children()[me.treeCol]);
        if($td.children('.indent-wrapper').length) { // inited?
            return;
        }
        $td[0].innerHTML = '<div class="indent-wrapper"><div class="indent"></div></div>' +
            '<div class="node-content cell-wrapper">' +
            '<div class="node-button"></div>' +
            $td[0].innerHTML + '</div>';
    };

    //ttt
    proto.updateAltRows = function () {
        if (!window.showAltRow) {
            return;
        }
        var me = this;
        var $trs = me.$table.children('tbody.nodes').children('tr:not(.hidden)');
        var visibleRowNo = 0;
        for (var n = 1; n < $trs.length; n++) {
            $tr = $($trs[n]);
            if (!$tr.hasClass('hidden')) {
                ++visibleRowNo;
                $tr.toggleClass('alt', (visibleRowNo % 2) != 0);
            }
        }
    };

    proto.updateIndentLinesExtended = function (trs) {
        var me = this;
        trs = trs.length ? trs : [trs];
        var n;

        var extendedTrs = [];
        for (n = trs.length - 1; n >= 0; --n) {
            var tr = trs[n];
            var parentTr = $(tr).data('parent');
            if (parentTr !== me.rootNode) {
                extendedTrs.push(parentTr);
                me.collectDescendants(parentTr, extendedTrs);

                var prevParentTr = domex.getPrevElement(parentTr);
                while (prevParentTr && $(prevParentTr).data('level') > 0) {
                    extendedTrs.push(prevParentTr);
                    prevParentTr = domex.getPrevElement(prevParentTr);
                }
            }
            else {
                extendedTrs.push(tr);
                me.collectDescendants(tr, extendedTrs);
            }

        }
        trs = jsex.distinct(extendedTrs);
        me.updateIndentLines(trs);
    };

    proto.updateIndentLines = function (tr) {
        var me = this;
        var n;
        if (!tr) {
            me.$table.children('tbody.nodes').children().each(function () {
                me.updateIndentLines(this);
            });
            return;
        }
        else if (tr.length) {
            for (n = 0; n < tr.length; n++) {
                me.updateIndentLines(tr[n]);
            }
            return;
        }
        var $tr = $(tr);
        var level = $tr.data('level');

        //var $indent = $tr.find('div.indent');
        var td = $tr.children()[me.treeCol];
        var indentEl = td.firstChild.firstChild;
        var $indent = $(indentEl);

        var html;
        if (!level) {
            html = '<img src="image/line-udr.gif"/>';
            if (!me.getNextSiblingNode(tr)) {
                html = '<img src="image/line-ur.gif"/>';
            }
            $indent[0].innerHTML = html;
//            $indent.html(html);
            return;
        }
        var parent = $tr.data('parent');
        var parentChildren = $(parent).data('children');
        var isLastChild = parentChildren[parentChildren.length - 1] === tr;
        html = isLastChild ? '<img src="image/line-ur.gif"/>' : '<img src="image/line-udr.gif"/>';
        while (parent !== me.rootNode) {
            var parentParent = $(parent).data('parent');
            var parentIsLastChild;
            if (parentParent === me.rootNode) {
                parentIsLastChild = !me.getNextSiblingNode(parent);
            }
            else {
                var parentParentChildren = $(parentParent).data('children');
                parentIsLastChild = parentParentChildren[parentParentChildren.length - 1] === parent;
            }
            var parentHtml = parentIsLastChild ?
                '<img src="image/line-na.gif"/>' :
                '<img src="image/line-ud.gif"/>';
            html = parentHtml + html;
            parent = parentParent;
        }
        $indent[0].innerHTML = html;
//        $indent.html(html);
    };

    //
    proto.setupLevel = function ($table, nodes) {
        var me = this;
        for (var n = 0; n < nodes.length; n++) {
            var $tr = $(nodes[n]);
            var children = $tr.data('children');

            //var $td = $tr.find('td:nth-child(' + (me.treeCol + 1) + ')');
            var $td = $($tr.children()[me.treeCol]);
            var $nodeContent = $td.children('.node-content');
            var $nodeButton = $($nodeContent.children()[0]);
            $nodeButton.css('margin-left', $tr.data('level') * levelIndent);
            if (children) {
                $tr.removeClass('leaf').addClass('parent');
                me.setupLevel($table, children);
                var expanded = $tr.data('expanded');
                if (expanded === undefined) {
                    expanded = 1;
                }
                expanded = parseInt(expanded);
                if (expanded) {
                    $tr.addClass('expanded');
                }
                else {
                    $tr.addClass('collapsed');
                    for (var m = 0; m < children.length; m++) {
                        me.hideBranch(children[m]);
                    }
                }
            }
            else {
                $tr.removeClass('parent expanded collapsed').addClass('leaf');
            }
        }
    };

    proto.getLastChild = function (node) {
        var children = $(node).data('children');
        if (children && children.length) {
            return children[children.length - 1];
        }
        return null;
    };

    proto.getParentNodes = function (nodes, selfIfRoot) {
        var me = this;
        var parentNodes = [];
        for (var n = 0; n < nodes.length; n++) {
            var parentNode = me.getParentNode(nodes[n]);
            if (parentNode === me.rootNode && selfIfRoot) {
                parentNode = nodes[n];
            }
            if ($.inArray(parentNode, parentNodes) == -1) {
                parentNodes.push(parentNode);
            }
        }
        return parentNodes;
    };

    proto.getParentNode = function (node) {
        var me = this;
        var $node = $(node);
        var result = $node.data('parent');
        return result || me.rootNode;
    };

    proto.isEmpty = function() {
        var me = this;
        return !domex.getNextElement(me.rootNode);
    };

    //
    proto.buildChildrenData = function (nodeAndDescendants, index) {
        var me = this;
        if (!nodeAndDescendants || nodeAndDescendants.length === 0) {
            return;
        }
        if (index === undefined) {
            index = 0;
        }
        var $node = $(nodeAndDescendants[index++]);
        var level = $node.data('level');
        var next = index < nodeAndDescendants.length ? nodeAndDescendants[index++] : null;
        var children = [];
        while (next) {
            var nextLevel = $(next).data('level');
            if (nextLevel <= level) {
                break;
            }
            if (nextLevel - level == 1) {
                var child = next;
                me.updateChildren(child);
                children.push(child);
                $(child).data('parent', $node[0]);
            }
            next = index < nodeAndDescendants.length ? nodeAndDescendants[index++] : null;
        }
        $node.data('children', children);
    };

    //
    proto.updateChildren = function (node, expandChildren) {
        var me = this;
        if (!node || node.length === 0) {
            return;
        }
        var $node = $(node);
        var level = $node.data('level');
        if (!level) {
//            $node.data('level', 0);
            $node.data('parent', me.rootNode);
        }
        var $next = $node.next();
        var children = [];
        while ($next.length) {
            var nextLevel = $next.data('level');
            if (nextLevel <= level) {
                break;
            }
            if (nextLevel - level == 1) {
                var child = $next[0];
                me.updateChildren(child);
                children.push(child);
                $(child).data('parent', node);
            }
            $next = $next.next();
        }
        $node.data('children', children);
        if($node[0] === this.rootNode) {
            //xx
        }
        else if (children.length) {
            $node.removeClass('leaf').addClass('parent');
            if (expandChildren) {
                $node.dataAttr('expanded', 1);
            }
            if ($node.data('expanded')) {
                $node.addClass('expanded').removeClass('collapsed');
                me.showBranch($node);
            }
            else {
                $node.addClass('collapsed').removeClass('expanded');
                me.hideDescendants($node);
            }
        }
        else {
            $node.removeClass('parent expanded collapsed').addClass('leaf');
        }
    };

    //
    proto.getNextSiblingNode = function (node) {
        var me = this;
        var $node = $(node);
        var parent = $node.data('parent');
        var children = $(parent).data('children');

        var index = jsex.indexOf(children, node);
        if (index == children.length - 1) {
            return null;
        }
        return children[index + 1];
    };

    proto.getNodeToActiveAfterDeletion = function (nodes) {
        var me = this;
        //assert no two nodes form a branch path
        var node0 = nodes[0];
        var resultNode;
        var node = node0;
        while (!resultNode && node) {
            node = me.getNextSiblingNode(node);
            if (jsex.indexOf(nodes, node, 1) == -1) {
                resultNode = node;
            }
        }
        node = node0;
        while (!resultNode && node) {
            node = me.getPrevSiblingNode(node);
            if (jsex.indexOf(nodes, node, 1) == -1) {
                resultNode = node;
            }
        }
        node = node0;
        while (!resultNode && node) {
            node = me.getParentNode(node);
            if (jsex.indexOf(nodes, node, 1) == -1) {
                resultNode = node;
            }
        }
        return resultNode;
    };

    proto.filterOutBranchDescendants = function (nodes) {
        var me = this;
        var allDescendants = [];
        var n;
        for (n = 0; n < nodes.length; n++) {
            me.collectDescendants(nodes[n], allDescendants);
        }
        for (n = nodes.length - 1; n >= 0; --n) {
            if (jsex.indexOf(allDescendants, nodes[n]) != -1) {
                nodes.splice(n, 1);
            }
        }
    };

    proto.filterOutBranchDescendantsForSortedNodes = function (nodes) {
        var me = this;
        var n = 0;
        while (n < nodes.length) {
            var descendants = me.collectDescendants(nodes[n++]);
            while ($.inArray(nodes[n], descendants) != -1 && n < nodes.length) {
                nodes.splice(n, 1);
            }
        }
    };

    proto.getPrevSiblingNode = function (node) {
        var $node = $(node);
        var level = $node.data('level');
        var $prev = $node.prev();
        while ($prev.length) {
            var nextLevel = $prev.data('level');
            if (nextLevel == level) {
                return $prev[0];
            }
            else if (nextLevel < level) {
                break;
            }
            $prev = $prev.prev();
        }
        return null;
    };

    proto.moveUp = function (node) {
        var me = this;
        node = $(node)[0];
        var prev = me.getPrevSiblingNode(node);
        if (!prev) {
            return;
        }
        var parent = me.getParentNode(node);
        var parentChildren = parent && $(parent).data('children');
        var index = jsex.indexOf(parentChildren, node);

        var tbody = node.parentNode;
        var descendants = [];
        me.collectDescendants(node, descendants);
        node = tbody.removeChild(node);

        var n;
        for (n = 0; n < descendants.length; n++) {
            tbody.removeChild(descendants[n]);
        }

        node = tbody.insertBefore(node, prev);

        for (n = 0; n < descendants.length; n++) {
            tbody.insertBefore(descendants[n], prev);
        }

        parentChildren.splice(index, 1);
        parentChildren.splice(index - 1, 0, node);
        me.updateIndentLinesExtended([prev, node]);
    };

    proto.moveDown = function (node) {
        var me = this;
        node = $(node)[0];
        var next = me.getNextSiblingNode(node);
        if (!next) {
            return;
        }

        var tbody = node.parentNode;
        var descendants = [];
        me.collectDescendants(next, descendants);
        next = tbody.removeChild(next);

        var n;
        for (n = 0; n < descendants.length; n++) {
            tbody.removeChild(descendants[n]);
        }

        next = tbody.insertBefore(next, node);

        for (n = 0; n < descendants.length; n++) {
            tbody.insertBefore(descendants[n], node);
        }

        //
        var parent = $(node).data('parent');
        var children = $(parent).data('children');
        var index = jsex.indexOf(children, node);
        children[index] = next;
        children[index + 1] = node;
        me.updateIndentLinesExtended([node, next]);
    };

    proto.collectDescendants = function (node, descendants) {
        var me = this;
        if (!descendants) {
            descendants = [];
        }
        var children = $(node).data('children') || [];
        for (var n = 0; n < children.length; n++) {
            var child = children[n];
            descendants.push(child);
            me.collectDescendants(child, descendants);
        }
        return descendants;
    };

    proto.indent = function (node) {
        var me = this;

        var $node = $(node);
        node = $node[0];
        var prev = me.getPrevSiblingNode(node);
        if (!prev) {
            return;
        }
        var parent = me.getParentNode(node);
        var parentChildren = $(parent).data('children');
        parentChildren.splice(jsex.indexOf(parentChildren, node), 1);

        var $prev = $(prev);
        var children;
        if ($prev.is('.leaf')) {
            $prev.removeClass('leaf').addClass('parent expanded').dataAttr('expanded', 1);
            children = [];
            $prev.data('children', children);
        }
        else {
            children = $prev.data('children');
            me.expandNode($prev);
        }
        children.push(node);
        $(node).data('parent', $prev[0]);
        me.updateBranchLevel(node, 1);
    };

    proto.unindent = function (node) {
        var me = this;

        var $node = $(node);
        node = $node[0];
        var parent = me.getParentNode(node);
        if (parent === me.rootNode) {
            return;
        }
        var parentChildren = $(parent).data('children');
        parentChildren.splice(jsex.indexOf(parentChildren, node), 1);
        if (!parentChildren.length) {
            $(parent).removeClass('parent expanded').addClass('leaf');
        }

        //*
        var tbody = node.parentNode;
        var descendants = [];
        me.collectDescendants(node, descendants);
        tbody.removeChild(node);
        var n;
        for (n = 0; n < descendants.length; n++) {
            tbody.removeChild(descendants[n]);
        }
        var beforeNode = null;

        var tParent = parent;
        while (tParent && beforeNode == null) {
            beforeNode = me.getNextSiblingNode(tParent);
            tParent = me.getParentNode(tParent);
        }

        tbody.insertBefore(node, beforeNode);
        for (n = 0; n < descendants.length; n++) {
            tbody.insertBefore(descendants[n], beforeNode);
        }
        //
        var grandParent = me.getParentNode(parent);
        $(node).data('parent', grandParent);
        var grandParentChildren = $(grandParent).data('children');
        grandParentChildren.splice(jsex.indexOf(grandParentChildren, parent) + 1, 0, node);

        //*/
        me.updateBranchLevel(node, -1);

        me.updateIndentLinesExtended([parent, node]);
    };

    proto.updateBranchLevel = function (node, diff) {
        var me = this;

        if (!diff) {
            return;
        }
        var $node = $(node);
        var children = $node.data('children') || [];
        for (var n = 0; n < children.length; n++) {
            me.updateBranchLevel(children[n], diff);
        }
        var level = diff + $node.data('level');

        $node.dataAttr('level', level);

        var $nodeContent = $node.find('.node-content');
        var $nodeButton = $($nodeContent.children()[0]);

        $nodeContent.css('padding-left', (levelIndent * level + 24) + 'px');
        $nodeButton.css('margin-left', level * levelIndent);
    };

    proto.deleteBranch = function (node) {
        var me = this;

        var $node = $(node);
        var children = $node.data('children') || [];
        for (var n = 0; n < children.length; n++) {
            me.deleteBranch(children[n]);
        }
        $node.remove();
    };

    proto.hideDescendants = function (node) {
        var me = this;
        var $node = node.length ? $(node) : $(node);
        var children = $node.data('children') || [];
        for (var n = 0; n < children.length; n++) {
            var $child = $(children[n]);
            me.hideDescendants($child);
            $child.addClass('hidden');
        }
    };

    proto.hideBranch = function (node) {
        var me = this;
        var $node = $(node);
        me.hideDescendants($node);
        $node.addClass('hidden');
    };

    proto.showBranch = function (node) {
        var me = this;
        var $node = $(node);
        $node.removeClass('hidden');

        if ($node.data('expanded')) {
            var children = $node.data('children') || [];
            for (var n = 0; n < children.length; n++) {
                me.showBranch(children[n]);
            }
        }
    };

    proto.expandNode = function (node, byCode) {
        var me = this;
        var $node = $(node);
        var expanded = $node.data('expanded');
        if (expanded) {
            return;
        }
        else {
            $node.removeClass('collapsed').addClass('expanded');
            $node.dataAttr('expanded', 1);
            me.showBranch($node);
        }
        if(!byCode) {
            //ttt
            me.updateAltRows();
            me.$table.trigger('rowsVisibilityChanged');
        }
    };

    proto.collapseNode = function (node, byCode) {
        var me = this;
        var $node = $(node);
        var expanded = $node.data('expanded');
        if (!expanded) {
            return;
        }
        else {
            $node.removeClass('expanded').addClass('collapsed');
            $node.dataAttr('expanded', 0);
            me.hideDescendants($node);
        }
        if(!byCode) {
            //ttt
            me.updateAltRows();
            me.$table.trigger('rowsVisibilityChanged');
        }
    };

    proto.toggleNode = function ($node) {
        var me = this;
        var expanded = $node.data('expanded');
        if (expanded) {
            me.collapseNode($node);
        }
        else {
            me.expandNode($node);
        }
//        me.$table.trigger('rowsVisibilityChanged');
    };

    /*assert nodes are branch separated and toNode is logically valid*/
    proto.changeNodesRelation = function (nodes, toNode, below, asChild) {
        var me = this;
        me.$table.trigger('treeChanging', [nodes]);

        var $toNode = $(toNode);
        var nodesToChange = [];
        var nodeParents = [];

        var rootChanged = !$toNode.data('level') && !asChild;
        for (var n = 0; n < nodes.length; n++) {
            var node = nodes[n];
            var nodeParent = me.getParentNode(node);
            if (!nodeParent) {
                rootChanged = true;
                nodeParents.push(node);
            }
            else {
                nodeParents.push(nodeParent);
            }

            var $node = $(node);
            var levelDiff = $toNode.data('level') - $node.data('level');
            if (asChild) {
                levelDiff += 1;
            }
            if (!levelDiff && (!asChild && !below) && $node.next().is(toNode)) {
                return [];
            }
            if (!levelDiff && (!asChild && below) && $node.prev().is(toNode)) {
                return [];
            }
            if (levelDiff) {
                me.updateBranchLevel(node, levelDiff);
            }
            nodesToChange.push(node);
            me.collectDescendants(node, nodesToChange);
        }

        $(nodesToChange).detach();

        if (!asChild && !below) {
            $toNode.before(nodesToChange);
        }
        else {
            if (jsex.inArray(toNode, nodeParents)) {
                me.updateChildren(toNode, true);
            }
            var toNodeDescendants = [toNode];
            me.collectDescendants(toNode, toNodeDescendants);
            if (asChild && !below) {
                $(toNode).after(nodesToChange);
            }
            else {
                $(toNodeDescendants[toNodeDescendants.length - 1]).after(nodesToChange);
            }
        }

        for (n = 0; n < nodeParents.length; n++) {
            me.updateChildren(nodeParents[n], false);
        }
        var toNodeParent = asChild ? toNode : me.getParentNode(toNode);
        if (toNodeParent && !jsex.inArray(toNodeParent, nodeParents)) {
            me.updateChildren(toNodeParent, false);
        }
        if (!toNodeParent) {
            for (n = 0; n < nodes.length; n++) {
                if (nodes[n] !== nodeParents[n]) {
                    me.updateChildren(nodes[n], false);
                }
            }
        }
        if (asChild) {
            me.expandNode(toNode);
        }
        //
//        me.updateIndentLines();

        me.$table.trigger('treeChanged');
        return nodesToChange;
    };

    //
    proto.expandAll = function () {
        var me = this;
        var $nodes = me.$table.find('tbody.nodes').children();
        for (var n = 1; n < $nodes.length; n++) {
            var $node = $($nodes[n]);
            if ($node.is('.parent') && !$node.data('expanded')) {
                me.expandNode($node, true);
            }
        }
        me.updateAltRows();
        me.$table.trigger('rowsVisibilityChanged');
    };

    proto.collapseAll = function () {
        var me = this;
        var $nodes = me.$table.find('tbody.nodes').children();
        for (var n = 1; n < $nodes.length; n++) {
            var $node = $($nodes[n]);
            if ($node.is('.parent') && $node.data('expanded')) {
                me.collapseNode($node, true);
            }
        }
        me.updateAltRows();
        me.$table.trigger('rowsVisibilityChanged');
    };

    function _findNodeByPathTitles(titles, level, nodes) {
        if(!nodes) {
            return null;
        }
        var title = titles[level];
        for(var n = 0; n < nodes.length; n++) {
            var node = nodes[n];
            var nodeTitle = $(node).find('.node-content').text();
            nodeTitle = jsex.trim(nodeTitle);
            if(nodeTitle == title) {
                if(level == titles.length - 1) {
                    return node;
                }
                var tNode = _findNodeByPathTitles(titles, level + 1, $(node).data('children'));
                if(tNode) {
                    return tNode;
                }
            }
        }
        return null;
    }

    //
    proto.findNodeByPathTitles = function(pathTitles) {
        var me = this;
        return _findNodeByPathTitles(pathTitles, 0, $(me.rootNode).data('children'));
    };

    proto.ensureNodeExpanded = function(node) {
        var me = this;
        var parent = $(node).data('parent');
        while(parent && parent !== me.rootNode) {
            me.expandNode(parent);
            parent = $(parent).data('parent');
        }
    };

    //

    $.fn.treeTable = $.addPluginFn(fnName, TreeTable, {
        debug: false
    });


})(jQuery, 'treeTable');